#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
"""
Spider style stacks
"""

from ..packlib.info import KING
from ..utillib.misc import kwdefault
from .. import pilelib as stack
from .. import hint
from .. import comparecards as cmp_cards
from .. import helpstrings as hs


class Hint(hint.CautiousDefaultHint):

    BONUS_SAME_SUIT_MOVE = 400

    def shall_move_pile(self, from_stack, to_stack, pile, rpile):
        """
        shall_move_pile(from_stack:Stack, to_stack:Stack, pile:seq(Card ...),
          rpile:seq(Card ...)) -> bool

        Returns **pile** builds on **to_stack**?

        Augment whether **pile** from **from_stack** builds on **to_stack** to
        calculate for possible play back of **pile**.
        """
        if not super(Hint, self).shall_move_pile(from_stack, to_stack, pile,
                                                 rpile):
            return False
        fc_stack = hint.AClonedStack.clone(from_stack, cards=rpile)
        if fc_stack.cardsBuild(to_stack, pile):
            base, top = pile[:1], to_stack[-1:]
            if not top or (fc_stack._sequence(base + top) and (not rpile or
                           fc_stack._sequence(base + rpile[-1:]))):
                return True
            if self._level <= 1 and not rpile:
                return True
            return False
        return True


class FoundationMethods(object):
    """
    Pile support accepts complete sequence of *RANKS* from another pile.

    :ivar string _BOTTOM_IMAGE_: use a suit bottom image for stack.
    """

    def __init__(self, x, y, game, suit, **cap):
        """
        __init__(x:int, y:int, game:obj, suit:int[, offset:tup=(0, 0),
          *capability* {max_move:int=0, max_accept:int=*RANKS*,
          base_rank:int=KING, dir:int=-1, min_accept:int=*RANKS*}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :param integer suit: pile's suit and base_suit
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.

        note:: modified max_accept and min_accept *capability* to size itself
          accoring to Game['suits'].
        """
        ranks = cmp_cards.highestRank(game, suit) + 1
        kwdefault(cap, suit=suit, base_rank=KING, dir=-1, min_accept=ranks,
                  max_accept=ranks, max_move=0)
        super(FoundationMethods, self).__init__(x, y, game, **cap)


class RkBuildMethod(object):
    """
    Supports seperate acceptable and moveable sequences. Cards accepted in
    rank order alone, but moves according to parent class.
    """

    def _build_sequence(self, cards):
        """
        .. method:: _build_sequence(cards:obj) -> bool

        :param sequence cards: cards from this pile
        :return: right sequence?
        :rtype: boolean
        """
        return cmp_cards.isRankSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_RK_DICT.get(cmp(self.cap['dir'], 0), '')


class SCBuildMethod(object):
    """
    Supports seperate acceptable and moveable sequences. Cards accepted in
    rank order alone, but moves according to parent class.
    """

    def _build_sequence(self, cards):
        """
        .. method:: _build_sequence(cards:obj) -> bool

        :param sequence cards: cards from this pile
        :return: right sequence?
        :rtype: boolean
        """
        return cmp_cards.isSameColorSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_SC_DICT.get(cmp(self.cap['dir'], 0), '')


class DropMethods(object):
    """
    Supports dropping complete rank sequence when Game.s.foundations accepts.
    """

    def cardsDrop(self, stacks, event=None):
        """
        cardsDrop(stacks:seq) -> seq

        :param sequence stacks: usually game.s.foundations
        :return: pile and number of cards.
        :rtype: tuple

        note:: modified to enable to drop complete *RANKS size accoring to
          Game['suits'].
        """
        ranks = cmp_cards.highestRank(self.game, self.TOPCARD['suit']) + 1
        if len(self.cards) > ranks:
            for s in stacks:
                if s is not self and s.cardsBuild(self, self.cards[-ranks:]):
                    return (s, ranks)
        return (None, 0)


class DropEitherMethods(object):
    """
    Supports dropping either complete rank sequence or a card when
    Game.s.foundations accepts.
    """

    def cardsDrop(self, stacks):
        """
        cardsDrop(stacks:seq) -> seq

        Overide to allow RANKS set or a single card drop.

        :param sequence stacks: usually game.s.foundations
        :return: pile and number of cards.
        :rtype: tuple
        """
        ranks = cmp_cards.highestRank(self.game, self.TOPCARD['suit']) + 1
        if not self.cards:
            return (None, 0)
        for s in stacks:
            for cards in (self.cards[-1:], self.cards[-ranks:]):
                if s is not self and s.cardsBuild(self, cards):
                    return (s, len(cards))
        return (None, 0)


# FOUNDATIONS::
#  Build full seq built down in rank by ... Play None.

class SS_Foundation(FoundationMethods, stack.SS_Foundation):
    """
    Build full seq built down in rank by same suit. Play none.
    """

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.DROP_SS_DICT.get(cmp(self.cap['dir'], 0), '')


class AC_Foundation(FoundationMethods, stack.AC_Foundation):
    """
    Build full seq built down in rank by alt color. Play none.
    """

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.DROP_AC_DICT.get(cmp(self.cap['dir'], 0), '')


class RK_Foundation(FoundationMethods, stack.RK_Foundation):
    """
    Build full seq built down in rank alone. Play none.
    """

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.DROP_RK_DICT.get(cmp(self.cap['dir'], 0), '')


# ROWSTACKS::
#   Build in rank alone. Play seq in rank ...

class SS_RK_RowStack(RkBuildMethod, stack.SS_RowStack):
    """
    Build in rank alone. Play seq in rank by same suit.
    """

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        build = super(SS_RK_RowStack, self)._helpString
        return "{0} {1}".format(build, hs.SPIDER_SS_STR).strip()


class SC_RK_RowStack(RkBuildMethod, stack.SC_RowStack):
    """
    Build in rank alone. Play seq in rank by same color.
    """

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        build = super(SC_RK_RowStack, self)._helpString
        return "{0} {1}".format(build, hs.SPIDER_SC_STR).strip()


class RankSS_RK_RowStack(SS_RK_RowStack):

    def _sequence(self, cards):
        """
        _sequence(cards:obj) -> bool

        :param sequence cards: cards from sending pile
        :return: right sequence?
        :rtype: boolean
        """
        return (cmp_cards.isSameSuit(cards) and
               cmp_cards.isSequenceWithRANKS(cards, self.game['ranks'],
                                             **self.cap))

    def _build_sequence(self, cards):
        """
        _build_sequence(cards:obj) -> bool

        :param sequence cards: cards from this pile
        :return: right sequence?
        :rtype: boolean
        """
        return cmp_cards.isSequenceWithRANKS(cards, self.game['ranks'],
                                             **self.cap)

# ROWSTACKS::
#   Build in rank alone by same color. Play seq in rank ...

class SS_SC_RowStack(SCBuildMethod, stack.SS_RowStack):
    """
    Build in rank by same suit. Play seq in rank by same color.
    """

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        build = super(SC_RK_RowStack, self)._helpString
        return "{0} {1}".format(build, hs.SPIDER_SC_STR).strip()


# ROWSTACKS::
#   Build in rank alone. Play seq in rank ... Drop full play seq.

class DropSS_RK_RowStack(DropMethods, SS_RK_RowStack):
    """
    Build in rank alone. Play seq in rank by same suit. Drop full seq in rank
    by same suit on _foundation_.
    """
    pass


class DropSC_RK_RowStack(DropMethods, SC_RK_RowStack):
    """
    Build in rank alone. Play seq in rank by same color. Drop full seq in rank
    by same color on _foundation_.
    """
    pass


# ROWSTACKS::
#   Build in rank by... Play seq. Drop full seq.

class DropAC_RowStack(DropMethods, stack.AC_RowStack):
    """
    Build in rank alone. Play seq in rank by alt color. Drop full seq in rank
    by alt color on _foundation_.
    """
    pass


class DropRK_RowStack(DropMethods, stack.RK_RowStack):
    """
    Build in rank alone. Play seq. Drop full seq in rank alone on
    _foundation_.
    """
    pass


# ROWSTACKS::
#   Build in rank by... Play seq. Drop full seq or top.

class DropEitherAC_RowStack(DropEitherMethods, stack.AC_RowStack):
    """
    Build in rank alone. Play seq in rank by alt color. Drop full seq in rank
    by alt color or single card on _foundation_.
    """
    pass


# ROWSTACKS::
#   Build in rank by... Play top or super move seq. Drop full seq.

class SuperMoveDropSS_RK_RowStack(stack.SuperMove_StackMethods,
                                   DropSS_RK_RowStack):
    """
    Build in rank alone. Play seq in rank by same suit equal to possible if
    played top only. Drop full seq in rank by same suit on _foundation_.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Check parent.cardsBuild() and seq has enough plays build individual.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts ``cards``?
        :rtype: boolean
        """
        if not super(SuperMoveDropSS_RK_RowStack, self).cardsBuild(from_stack,
                                                                   cards):
            return False
        num_seq = self._getNumSSSeq(cards)
        max_move = self._getMaxMove(len(self.cards))
        return num_seq <= max_move

    def cardsPlay(self, cards):
        """
        cardsPlay(cards:tup) -> bool

        Check parent.cardsPlay() and seq has enough plays play individual.

        :param tuple cards: cards to move from this pile
        :return: cards moveable?
        :rtype: boolean
        """
        if self.cap.sendCards(self, cards):
            return False
        if not cmp_cards.isRankSequence(cards, self.cap['mod'], self.cap['dir']):
            return False
        num_seq = self._getNumSSSeq(cards)
        max_move = self._getMaxMove(1)
        return num_seq <= max_move